我們要封裝 Texture 的目的與 Shader 相同,所以我們就趕緊開始封裝吧。
Texture 的封裝比較簡單。
class Texture2D {
public:
Texture2D() = default;
Texture2D(const std::string& path);
~Texture2D();
uint32_t getWidth() const { return width_; }
uint32_t getHeight() const { return height_; }
void bind(uint32_t slot = 0) const;
void unbind() const;
private:
std::string path_;
uint32_t width_;
uint32_t height_;
uint32_t textureID_;
};
一樣它需要一個屬於自己的 ID。
而我們在 construct 的時候一樣傳入我們影像的路徑。
Texture2D::Texture2D(const std::string& path) : path_(path){
sf::Image image;
if(!image.loadFromFile(path)) printf("[Texture2D] Error on loading image");
// image.flipVertically();
width_ = image.getSize().x;
height_ = image.getSize().y;
glCreateTextures(GL_TEXTURE_2D, 1, &textureID_);
glBindTexture(GL_TEXTURE_2D, textureID_);
glTextureStorage2D(textureID_, 1, GL_RGBA8, width_, height_);
glTextureParameteri(textureID_, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTextureParameteri(textureID_, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureSubImage2D(textureID_, 0, 0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)image.getPixelsPtr());
glBindTexture(GL_TEXTURE_2D, 0);
}
bind/unbind
就一樣的簡單。
void Texture2D::bind(uint32_t slot) const {
glBindTextureUnit(slot, textureID_);
}
void Texture2D::unbind() const{
glBindTextureUnit(0, 0);
}
使用方法
Texture texture("picture.png");
現在我們有 Texture 跟 Shader 的管理器了,但是他們仍需要寫一些 code 來初始化它。當然我們可以很輕鬆地將文件加載的功能寫入各自的功能當中,但這樣耦合性有點太高了。這兩個物件應該只關自己本身該關注的事情,而不是文件加載。
出於這個原因,我們需要更好的方法來實現文件加載。ResourceManager
就是專門加載遊戲相關資源的管理器。
實作方法有很多種,這裡使用單利模式(Singleton)來實作。它將在整個遊戲中都能夠使用。
在實作前,有時候會不知道怎麼寫比較好。
其實有個辦法,先想像你希望API用起來是如何。
像是我希望我的資源管理器用起來是這樣。
ResourceManager::loadShader("vShader.glsl", "fShader.glsl", "sprite");
auto& shader = ResourceManager::getShader("sprite");
ResourceManager::loadTexture("gardevoir.png", "gardevoir");
auto &texture = texture_ = ResourceManager::getTexture( "gardevoir");
我能夠輕易地將 vertexShader, fragmentShader 加載至資源管理器中,並且調用ResourceManager::getShader
來獲得我的 Shader, Texture同理。
所以根據我的想像,我能夠輕易地將介面寫出來。
class ResourceManager {
public:
static std::map<std::string, std::shared_ptr<Shader>> shader;
static std::map<std::string, std::shared_ptr<Texture2D>> texture;
static std::shared_ptr<Shader>& loadShader(const std::string& vShaderPath, const std::string& fShaderPath, std::string name);
static std::shared_ptr<Shader>& getShader(std::string name);
static std::shared_ptr<Texture2D>& loadTexture(const std::string& tPath, std::string name);
static std::shared_ptr<Texture2D>& getTexture(std::string name);
static void clear();
private:
ResourceManager();
};
接著將它實作
std::map<std::string, std::shared_ptr<Shader>> ResourceManager::shader;
std::map<std::string, std::shared_ptr<Texture2D>> ResourceManager::texture;
std::shared_ptr<Shader>& ResourceManager::loadShader(const std::string& vShaderPath, const std::string& fShaderPath, std::string name) {
shader[name] = std::make_shared<Shader>(vShaderPath.c_str(), fShaderPath.c_str());
return shader[name];
}
std::shared_ptr<Shader>& ResourceManager::getShader(std::string name) {
return shader[name];
}
std::shared_ptr<Texture2D>& ResourceManager::loadTexture(const std::string& tPath, std::string name) {
texture[name] = std::make_shared<Texture2D>(tPath);
return texture[name];
}
std::shared_ptr<Texture2D>& ResourceManager::getTexture(std::string name) {
return texture[name];
}
void ResourceManager::clear() {
shader.clear();
texture.clear();
}
這樣就完成我們簡易的資源管理器啦!